﻿using System.Net.Sockets;
using System.Threading;
using System.IO;
using System.Text;
using System;
using System.Collections;
using System.Collections.Generic;
using MudEngine.Runtime;
using System.Net;

namespace MudEngine
{
	public delegate void ConnectEventHandler(TcpConnection sender);
	public delegate void TelnetEventHandler(TcpConnection sender, TelnetEventArgs args);

	public class TcpConnection
	{
		public event ConnectEventHandler OnConnect;
        public event ConnectEventHandler OnDisconnect;
        public event TelnetEventHandler OnTelnetCommand;
		/// <summary>
		/// 
		/// </summary>
		private TcpClient _client;
        /// <summary>
        /// 
        /// </summary>
        public int ConnectionId { get; set; }
		/// <summary>
		/// 
		/// </summary>
		private NetworkStream _stream;
		/// <summary>
		/// 
		/// </summary>
        public object_t Object { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int Port
        {
            get
            {
                return ((IPEndPoint)_client.Client.RemoteEndPoint).Port;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        private string _inputTo;
		/// <summary>
		/// 
		/// </summary>
		private bool isActive = false;
        /// <summary>
        /// 
        /// </summary>
        private Queue<byte[]> _sendQueue = new Queue<byte[]>(10);
		/// <summary>
		/// 
		/// </summary>
		private Thread receiverThread = null;
        /// <summary>
        /// 
        /// </summary>
        private Thread senderThread = null;
        /// <summary>
        /// 
        /// </summary>
        private Queue<TcpMessage> _queue = new Queue<TcpMessage>();
        /// <summary>
		/// 
		/// </summary>
		/// <param name="manager"></param>
		/// <param name="client"></param>
		public TcpConnection(TcpClient client)
		{
			this._client = client;
		}
		/// <summary>
		/// 
		/// </summary>
        public void HandleConnection()
		{
			Log.Info(this.GetType(), "New client accepted.");

			this._stream = this._client.GetStream();

			isActive = true;

			receiverThread = new Thread(new ThreadStart(ReceiveData));
			receiverThread.Start();

			senderThread = new Thread(new ThreadStart(SendData));
			senderThread.Start();

            if (OnConnect != null)
				OnConnect(this);
		}
		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public bool Disconnect()
		{
            if (isActive)
            {
                isActive = false;
                this._stream.Flush();
                this._stream.Close();

                if (OnDisconnect != null)
                    OnDisconnect(this);
            }
			return true;
		}
        /// <summary>
        /// 
        /// </summary>
        public bool HasMessage
        {
            get
            {
                return _queue.Count > 0;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public TcpMessage GetNextMessage()
        {
            if (!HasMessage)
                return null;
            return _queue.Dequeue();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="msg"></param>
        public void SendData(string msg)
        {
            this._sendQueue.Enqueue(Encoding.Unicode.GetBytes(msg));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="data"></param>
        public void SendData(byte[] data)
        {
            this._sendQueue.Enqueue(data);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="echo"></param>
        /// <returns></returns>
        public bool Send(bool echo)
        {
            byte[] b = new byte[] { Telnet.IAC, (echo ? Telnet.CMD_WILL : Telnet.CMD_WONT), Telnet.OPT_ECHO };
            this.SendData(b);

            return true;
        }
        /// <summary>
        /// 
        /// </summary>
        private void SendData()
        {
            byte[] b = new byte[0];
            while (isActive)
            {
                if (_sendQueue.Count > 0)
                {
                    b = _sendQueue.Dequeue();
                    SendData(b, 0, b.Length);
                }
                else
                    Thread.Sleep(5);
            }
        }
		/// <summary>
		/// 
		/// </summary>
		/// <param name="data"></param>
		/// <param name="offset"></param>
		/// <param name="size"></param>
		private void SendData(byte[] data, int offset, int size)
		{
			if (isActive)
				this._stream.Write(data, offset, size);
		}
		/// <summary>
		/// 
		/// </summary>
		private void ReceiveData()
		{
			TcpMessage currentMessage;
			string currentLine;

			ConnState state = ConnState.DATA;
			TelnetCommand cmd = null;
			int b = -1;
			int n = 0;
			char[] buffer = new char[512];
			try
			{
				while ((b = this._stream.ReadByte()) != -1 && isActive)
				{
					switch (state)
					{
						case ConnState.DATA:
							switch (b)
							{
								case Telnet.IAC:
									state = ConnState.IAC;
									break;
								case Telnet.CR:
									break;
								case Telnet.LF:
									currentLine = new String(buffer, 0, n);

									currentMessage = new TcpMessage(this, currentLine);
                                    _queue.Enqueue(currentMessage);

									n = 0;
									break;
								default:
									buffer[n++] = (char)b;
									break;
							}
							break;
						case ConnState.IAC:
							switch (b)
							{
								case Telnet.CMD_DO:
									state = ConnState.DO;
									break;
								case Telnet.CMD_DONT:
									state = ConnState.DONT;
									break;
								case Telnet.CMD_WILL:
									state = ConnState.WILL;
									break;
								case Telnet.CMD_WONT:
									state = ConnState.WONT;
									break;
								case Telnet.CMD_SB:
									state = ConnState.SB;
									cmd = new TelnetCommand();
									break;
								case Telnet.CMD_IP:
									state = ConnState.DATA;
									break;
								case Telnet.CMD_BREAK:
									state = ConnState.DATA;
									break;
								case Telnet.CMD_AYT:
									state = ConnState.DATA;
									break;
								default:
									state = ConnState.DATA;
									break;
							}
							break;
						case ConnState.DO:
							switch (b)
							{
								case Telnet.OPT_ECHO:
									break;
								default:
									break;
							}
							state = ConnState.DATA;
							break;
						case ConnState.DONT:
							switch (b)
							{
								case Telnet.OPT_ECHO:
									break;
								default:
									break;
							}
							state = ConnState.DATA;
							break;
						case ConnState.WILL:
							switch (b)
							{
								case Telnet.OPT_NEGWINSIZE:
									SendData(new byte[] { Telnet.IAC, Telnet.CMD_DO, Telnet.OPT_NEGWINSIZE }, 0, 3);
									break;
								default:
									break;
							}
							state = ConnState.DATA;
							break;
						case ConnState.WONT:
							state = ConnState.DATA;
							break;
						case ConnState.SB:
							if (cmd != null)
								cmd.Set(b);
							if (b == Telnet.IAC)
								state = ConnState.SE;
							break;
						case ConnState.SE:
							if (b == Telnet.CMD_SE)
							{
								this.HandleCommand(cmd);
								cmd = null;

								state = ConnState.DATA;
							}
							else
								state = ConnState.SB;
							break;
						default:
							break;
					}
				}
			}
			catch (Exception e)
			{
                Log.Debug(typeof(TcpConnection), "Could not read from stream. Client got disconnected.", e);
			}
            this.Disconnect();
			Log.Info(this.GetType(), "Client disconnected.");
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="command"></param>
		private void HandleCommand(TelnetCommand command)
		{
			if (command == null)
				return;

			switch (command.Params[0])
			{
				case Telnet.OPT_NEGWINSIZE:
					int width = (command.Params[1] << 8) + command.Params[2];
					int height = (command.Params[3] << 8) + command.Params[4];

					if (OnTelnetCommand != null)
						OnTelnetCommand(this, new TelnetEventArgs(width, height));

					break;
				default:
					break;
			}
		}
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public bool HasInputTo()
        {
            return !string.IsNullOrWhiteSpace(this._inputTo);
        }
        /// <summary>
        /// 
        /// </summary>
        public string InputTo
        {
            get
            {
                string it = this._inputTo;
                this._inputTo = null;
                return it;
            }
            set
            {
                this._inputTo = value;
            }
        }

		private class TelnetCommand
		{
			private int _p;
			public byte[] Params = new byte[10];

			public int Length
			{
				get
				{
					return _p;
				}
			}

			public void Set(int p)
			{
				this.Params[_p++] = (byte)p;
			}
		}

		private enum ConnState
		{
			DATA,
			IAC,
			SB,
			SE,
			DO,
			DONT,
			WILL,
			WONT
		}
	}

	public class TelnetEventArgs
	{
		private int width;
		private int height;

		public TelnetEventArgs(int width, int height)
		{
			this.width = width;
			this.height = height;
		}

	}
}
